/******************************************************************************* * Copyright (C) 2014 The Calrissian Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package org.calrissian.restdoclet.collector; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.ProgramElementDoc; import com.sun.javadoc.RootDoc; import org.calrissian.restdoclet.model.*; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import static java.util.Collections.emptyList; import static org.calrissian.restdoclet.util.CommonUtils.*; import static org.calrissian.restdoclet.util.TagUtils.*; public abstract class AbstractCollector implements Collector { protected abstract boolean shouldIgnoreClass(ClassDoc classDoc); protected abstract boolean shouldIgnoreMethod(MethodDoc methodDoc); protected abstract EndpointMapping getEndpointMapping(ProgramElementDoc doc); protected abstract Collection<PathVar> generatePathVars(MethodDoc methodDoc); protected abstract Collection<QueryParam> generateQueryParams(MethodDoc methodDoc); protected abstract RequestBody generateRequestBody(MethodDoc methodDoc); /** * Will generate and aggregate all the rest endpoint class descriptors. * @param rootDoc * @return */ @Override public Collection<ClassDescriptor> getDescriptors(RootDoc rootDoc) { Collection<ClassDescriptor> classDescriptors = new ArrayList<ClassDescriptor>(); //Loop through all of the classes and if it contains endpoints then add it to the set of descriptors. for (ClassDoc classDoc : rootDoc.classes()) { ClassDescriptor descriptor = getClassDescriptor(classDoc); if (descriptor != null && !isEmpty(descriptor.getEndpoints())) classDescriptors.add(descriptor); } return classDescriptors; } /** * Will generate a single class descriptor and all the endpoints for that class. * * If any class contains the special javadoc tag {@link org.calrissian.restdoclet.util.TagUtils.IGNORE_TAG} it will be excluded. * @param classDoc * @return */ protected ClassDescriptor getClassDescriptor(ClassDoc classDoc) { //If the ignore tag is present or this type of class should be ignored then simply ignore this class if (!isEmpty(classDoc.tags(IGNORE_TAG)) || shouldIgnoreClass(classDoc)) return null; String contextPath = getContextPath(classDoc); Collection<Endpoint> endpoints = getAllEndpoints(contextPath, classDoc, getEndpointMapping(classDoc)); //If there are no endpoints then no use in providing documentation. if (isEmpty(endpoints)) return null; String name = getClassName(classDoc); String description = getClassDescription(classDoc); return new ClassDescriptor( (name == null ? "" : name), (contextPath == null ? "" : contextPath), endpoints, (description == null ? "" : description) ); } /** * Retrieves all the end point provided in the specified class doc. * @param contextPath * @param classDoc * @param classMapping * @return */ protected Collection<Endpoint> getAllEndpoints(String contextPath, ClassDoc classDoc, EndpointMapping classMapping) { Collection<Endpoint> endpoints = new ArrayList<Endpoint>(); for (MethodDoc method : classDoc.methods(true)) endpoints.addAll(getEndpoint(contextPath, classMapping, method)); //Check super classes for inherited methods if (classDoc.superclass() != null) endpoints.addAll(getAllEndpoints(contextPath, classDoc.superclass(), classMapping)); return endpoints; } /** * Retrieves the endpoint for a single method. * * If any method contains the special javadoc tag {@link org.calrissian.restdoclet.util.TagUtils.IGNORE_TAG} it will be excluded. * @param contextPath * @param classMapping * @param method * @return */ protected Collection<Endpoint> getEndpoint(String contextPath, EndpointMapping classMapping, MethodDoc method) { //If the ignore tag is present then simply return nothing for this endpoint. if (!isEmpty(method.tags(IGNORE_TAG)) || shouldIgnoreMethod(method)) return emptyList(); Collection<Endpoint> endpoints = new ArrayList<Endpoint>(); EndpointMapping methodMapping = getEndpointMapping(method); Collection<String> paths = resolvePaths(contextPath, classMapping, methodMapping); Collection<String> httpMethods = resolveHttpMethods(classMapping, methodMapping); Collection<String> consumes = resolveConsumesInfo(classMapping, methodMapping); Collection<String> produces = resolvesProducesInfo(classMapping, methodMapping); Collection<PathVar> pathVars = generatePathVars(method); Collection<QueryParam> queryParams = generateQueryParams(method); RequestBody requestBody = generateRequestBody(method); for (String httpMethod : httpMethods) for (String path : paths) endpoints.add( new Endpoint( path, httpMethod, queryParams, pathVars, requestBody, consumes, produces, method.commentText(), firstSentence(method), method.returnType() ) ); return endpoints; } /** * Will get the initial context path to use for all rest endpoint. * * This looks for the value in a special javadoc tag {@link org.calrissian.restdoclet.util.TagUtils.CONTEXT_TAG} * * @param classDoc * @return */ protected String getContextPath(ClassDoc classDoc) { if(!isEmpty(classDoc.tags(CONTEXT_TAG))) return classDoc.tags(CONTEXT_TAG)[0].text(); return ""; } /** * Will get the display name for the class. * * This looks for the value in a special javadoc tag {@link org.calrissian.restdoclet.util.TagUtils.NAME_TAG} * * @param classDoc * @return */ protected String getClassName(ClassDoc classDoc) { if (!isEmpty(classDoc.tags(NAME_TAG))) return classDoc.tags(NAME_TAG)[0].text(); return classDoc.typeName(); } /** * Will get the description for the class. * @param classDoc * @return */ protected String getClassDescription(ClassDoc classDoc) { return classDoc.commentText(); } /** * Will generate all the paths specified in the class and method mappings. * Each path should start with the context path, followed by one of the class paths, * then finally the method path. * * @param contextPath * @param classMapping * @param methodMapping * @return */ protected Collection<String> resolvePaths(String contextPath, EndpointMapping classMapping, EndpointMapping methodMapping) { contextPath = (contextPath == null ? "" : contextPath); //Build all the paths based on the class level, plus the method extensions. LinkedHashSet<String> paths = new LinkedHashSet<String>(); if (isEmpty(classMapping.getPaths())) { for (String path : methodMapping.getPaths()) paths.add(fixPath(contextPath + path)); } else if (isEmpty(methodMapping.getPaths())) { for (String path : classMapping.getPaths()) paths.add(fixPath(contextPath + path)); } else { for (String defaultPath : classMapping.getPaths()) for (String path : methodMapping.getPaths()) paths.add(fixPath(contextPath + defaultPath + path)); } return paths; } /** * Will use the method's mapped information if it is not empty, otherwise it will use the class mapping information * to retrieve all the https methods. * @param classMapping * @param methodMapping * @return */ protected Collection<String> resolveHttpMethods(EndpointMapping classMapping, EndpointMapping methodMapping) { return firstNonEmpty( methodMapping.getHttpMethods(), classMapping.getHttpMethods() ); } /** * Will use the method's mapped information if it is not empty, otherwise it will use the class mapping information * to retrieve all the consumeable information. * @param classMapping * @param methodMapping * @return */ protected Collection<String> resolveConsumesInfo(EndpointMapping classMapping, EndpointMapping methodMapping) { return firstNonEmpty( methodMapping.getConsumes(), classMapping.getConsumes() ); } /** * Will use the method's mapped information if it is not empty, otherwise it will use the class mapping information * to retrieve all the produceable information. * @param classMapping * @param methodMapping * @return */ protected Collection<String> resolvesProducesInfo(EndpointMapping classMapping, EndpointMapping methodMapping) { return firstNonEmpty( methodMapping.getProduces(), classMapping.getProduces() ); } }